package com.ruoyi.web.controller.system;

import cn.dev33.satoken.stp.StpUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.ruoyi.common.annotation.Log;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.enums.BusinessType;
import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.system.domain.VideoTask;
import com.ruoyi.system.mapper.VideoTaskMapper;
import com.ruoyi.system.service.IVideoTaskService;
import org.apache.poi.ss.usermodel.*;
import org.apache.poi.xssf.usermodel.XSSFWorkbook;
import org.apache.shiro.authz.annotation.RequiresPermissions;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.ModelMap;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.math.BigDecimal;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Path;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import java.util.zip.ZipOutputStream;

/**
 * 任务管理Controller
 *
 * @author wsx
 * @date 2025-07-30
 */

@Controller
@RequestMapping("/system/videoTask")
public class VideoTaskController extends BaseController {
    private String prefix = "system/videoTask";

    @Value("${ffmpeg.savePath}")
    private String localVideoDir;

    @Autowired
    private IVideoTaskService videoTaskService;

    @RequiresPermissions("system:videoTask:view")
    @GetMapping()
    public String videoTask() {
        return prefix + "/videoTask";
    }

    /**
     * 查询任务管理列表
     */
//     @RequiresPermissions("system:videoTask:list")
    @PostMapping("/list")
    @ResponseBody
    public TableDataInfo list(VideoTask videoTask) {
//        Integer loginId = StpUtil.getLoginIdAsInt();
//        videoTask.setLoginId(loginId);
        startPage();
        List<VideoTask> list = videoTaskService.selectVideoTaskList(videoTask);
        return getDataTable(list);
    }

    /**
     * 导出任务管理列表
     */
    @RequiresPermissions("system:videoTask:export")
    @Log(title = "任务管理", businessType = BusinessType.EXPORT)
    @PostMapping("/export")
    @ResponseBody
    public AjaxResult export(VideoTask videoTask) {
        List<VideoTask> list = videoTaskService.selectVideoTaskList(videoTask);
        ExcelUtil<VideoTask> util = new ExcelUtil<VideoTask>(VideoTask.class);
        return util.exportExcel(list, "任务管理数据");
    }

    /**
     * 新增任务管理
     */
    @RequiresPermissions("system:videoTask:add")
    @GetMapping("/add")
    public String add() {
        return prefix + "/add";
    }

    /**
     * 新增保存任务管理
     */
    // @RequiresPermissions("system:videoTask:add")
    @Log(title = "任务管理", businessType = BusinessType.INSERT)
    @PostMapping("/add")
    @ResponseBody
    public AjaxResult addSave(VideoTask videoTask) {
        if (videoTask.getTaskType() == null) {
            return error("请选择任务类型");
        }
        return toAjax(videoTaskService.insertVideoTask(videoTask));
    }

    /**
     * 修改任务管理
     */
    @RequiresPermissions("system:videoTask:edit")
    @GetMapping("/edit/{taskId}")
    public String edit(@PathVariable("taskId") Long taskId, ModelMap mmap) {
        VideoTask videoTask = videoTaskService.selectVideoTaskByTaskId(taskId);
        mmap.put("videoTask", videoTask);
        return prefix + "/edit";
    }

    /**
     * 修改保存任务管理
     */
    // @RequiresPermissions("system:videoTask:edit")
    @Log(title = "任务管理", businessType = BusinessType.UPDATE)
    @PostMapping("/edit")
    @ResponseBody
    public AjaxResult editSave(VideoTask videoTask) {
        return toAjax(videoTaskService.updateVideoTask(videoTask));
    }

    /**
     * 删除任务管理
     */
    // @RequiresPermissions("system:videoTask:remove")
    @Log(title = "任务管理", businessType = BusinessType.DELETE)
    @PostMapping("/remove")
    @ResponseBody
    public AjaxResult remove(String ids) {
        return AjaxResult.success(videoTaskService.updateDelFlag(ids));
    }

    /**
     * 下拉任务管理
     *
     * @return
     */
    @PostMapping("/selectTask")
    @ResponseBody
    public AjaxResult selectTask() {
        return AjaxResult.success(videoTaskService.selectVideoTaskList(new VideoTask()));
    }

    /**
     * 详情接口
     */
    @PostMapping("/taskDetail")
    @ResponseBody
    public AjaxResult taskDetail(Long taskId) {
        return AjaxResult.success(videoTaskService.selectVideoTaskByTaskId(taskId));
    }


    @GetMapping("addAnaDataLog")
    @ResponseBody
    public AjaxResult addAnaDataLog(Long dataId,String operation,String filePath,String operationType){
        Integer loginId = StpUtil.getLoginIdAsInt();
        Map<String, Object> map = new HashMap<>();
        map.put("data_id",dataId);
        map.put("operation",operation);
        map.put("file_path",filePath);
        map.put("create_id",loginId);
        map.put("operation_type",operationType);
        videoTaskMapper.insetAnaDataLog(map);
        return AjaxResult.success("操作成功！");
    }



    @GetMapping("getDataLogById")
    @ResponseBody
    public AjaxResult getDataLogById(Long id){
        List<Map<String, Object>> list = videoTaskMapper.getDataLogById(id);
        return AjaxResult.success(list);
    }

    @Autowired
    private VideoTaskMapper videoTaskMapper;


    //***************************************  导出  **************************************************


//    @GetMapping("exportVideoDataTask")
//    @ResponseBody
//    public void exportVideoDataTask(@RequestParam Long taskId, HttpServletResponse response) {
//        // 1. 获取数据
//        Map<String, Object> taskMap = videoTaskMapper.getVideoTaskById(taskId);
//        List<Map<String, Object>> joinList = videoTaskMapper.getVideoTaskJoinByTaskId(taskId);
//        List<Map<String, Object>> dataList = videoTaskMapper.getVideoDataByTaskId(taskId);
//
//        System.err.println("taskMap----------" + taskMap);
//        System.err.println("dataList----------" + dataList);
//        System.err.println("joinList----------" + joinList);
//
//        // 2. 创建临时文件用于处理（优化跨系统兼容性）
//        File tempDir = null;
//        File excelFile = null;
//        try {
//            // 显式使用系统临时目录，避免权限问题
//            String systemTempDir = System.getProperty("java.io.tmpdir");
//            tempDir = new File(systemTempDir, "video_export_" + System.currentTimeMillis());
//            if (!tempDir.exists() && !tempDir.mkdirs()) {
//                throw new IOException("无法创建临时目录：" + tempDir.getAbsolutePath());
//            }
//
//            // 3. 生成Excel文件
//            excelFile = new File(tempDir, "video_data.xlsx");
//            generateExcel(taskMap, joinList, dataList, excelFile);
//
//            // 4. 准备视频文件（支持目录）
//            File videoDir = new File(tempDir, "videos");
//            if (!videoDir.mkdir()) {
//                throw new IOException("无法创建视频临时目录");
//            }
//            copyVideoDirectories(dataList, videoDir);
//
//            // 5. 打包成压缩包并响应（补充兼容头）
//            response.setContentType("application/zip");
//            response.setHeader("Pragma", "public");
//            response.setHeader("Cache-Control", "max-age=0");
//            response.setHeader("Access-Control-Allow-Credentials", "true"); // 跨域兼容
//
//            String zipFileName = "video_task_" + taskId + "_export.zip";
//            String encodedFileName = URLEncoder.encode(zipFileName, StandardCharsets.UTF_8.name());
//            response.setHeader("Content-Disposition", "attachment; filename=\"" + encodedFileName + "\"");
//
//            try (ZipOutputStream zos = new ZipOutputStream(response.getOutputStream())) {
//                addFileToZip(zos, excelFile, "");
//                addDirectoryToZip(zos, videoDir, "videos");
//            }
//        } catch (Exception e) {
//            System.err.println("导出视频数据失败:" + e);
//            e.printStackTrace();
//            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
//
//            // 异常时清空流，避免EXE接收残缺数据
//            try {
//                if (response.getOutputStream() != null) {
//                    response.getOutputStream().flush();
//                    response.getOutputStream().close();
//                }
//            } catch (IOException ex) {
//                ex.printStackTrace();
//            }
//        } finally {
//            // 清理临时文件
//            if (excelFile != null && excelFile.exists() && !excelFile.delete()) {
//                System.err.println("无法删除临时Excel文件: " + excelFile.getAbsolutePath());
//            }
//            if (tempDir != null) {
//                deleteDirectory(tempDir);
//            }
//        }
//    }


    @GetMapping("exportVideoDataTaskNew")
    @ResponseBody
    public AjaxResult exportVideoDataTaskNew(@RequestParam String taskIds) {
        // 1. 校验并解析多任务ID（英文逗号分隔）
        if (taskIds == null || taskIds.trim().isEmpty()) {
            return AjaxResult.error("参数错误：taskIds不能为空（格式：1,2,3）");
        }
        List<Long> taskIdList;
        try {
            taskIdList = Arrays.stream(taskIds.split(","))
                    .map(String::trim)
                    .filter(id -> !id.isEmpty())
                    .map(Long::valueOf)
                    .distinct()
                    .collect(Collectors.toList());
        } catch (NumberFormatException e) {
            return AjaxResult.error("参数错误：taskIds包含非数字（格式：1,2,3）");
        }
        if (taskIdList.isEmpty()) {
            return AjaxResult.error("参数错误：taskIds格式无效（格式：1,2,3）");
        }

        // 2. 定义基础目录（建议配置在application.properties中，此处保留原逻辑）
        File baseDir = new File(localVideoDir, "temporaryDirectory");
        if (!baseDir.exists() && !baseDir.mkdirs()) {
            return AjaxResult.error("无法创建基础目录: " + baseDir.getAbsolutePath());
        }

        // 3. 批量获取多任务数据（Mapper方法已适配List<Long>入参）
        List<Map<String, Object>> taskMapList = videoTaskMapper.getVideoTaskById(taskIdList); // 多任务列表
        List<Map<String, Object>> joinList = videoTaskMapper.getVideoTaskJoinByTaskId(taskIdList); // 多任务关联数据
        List<Map<String, Object>> dataList = videoTaskMapper.getVideoDataByTaskId(taskIdList); // 多任务视频数据（含telemetry_storage_path）
        List<Map<String, Object>> telemetryDataList = videoTaskMapper.getTelemetryDataByTaskId(taskIdList); // 遥测数据

        // 4. 校验数据有效性
        if (taskMapList.isEmpty()) {
            return AjaxResult.error("未找到对应任务数据（taskIds：" + taskIds + "）");
        }

        // 5. 定义压缩包文件名（多任务场景：用“任务ID-连接”命名，避免重复）
        String taskIdStr = taskIdList.stream().map(String::valueOf).collect(Collectors.joining("-"));
        String zipFileName = "video_task_" + taskIdStr + "_export.zip";
        File zipFile = new File(baseDir, zipFileName);

        // 6. 创建临时工作目录（用于生成Excel和临时视频文件）
        File tempWorkDir = null;
        try {
            // 生成临时目录（File.createTempFile会创建文件，需删除后再创建目录）
            tempWorkDir = File.createTempFile("video_work_", "_temp");
            if (!tempWorkDir.delete() || !tempWorkDir.mkdir()) {
                throw new IOException("无法创建临时工作目录：" + tempWorkDir.getAbsolutePath());
            }

            // 7. 生成多任务Excel（核心改造：入参改为List<Map>）
            File excelFile = new File(tempWorkDir, "video_data.xlsx");
            generateExcel(taskMapList, joinList, dataList, telemetryDataList, excelFile); // 改造后方法

            // 8. 复制多任务视频文件（原有逻辑不变）
            File videoDir = new File(tempWorkDir, "videos");
            if (!videoDir.mkdir()) {
                throw new IOException("无法创建视频临时目录：" + videoDir.getAbsolutePath());
            }
            copyVideoDirectories(dataList, videoDir);

            // 新增：8.1 复制多任务遥测目录（从dataList提取telemetry_storage_path）
            File telemetryDir = new File(tempWorkDir, "telemetries");
            copyTelemetryDirectories(dataList, telemetryDir);

            // 9. 打包Excel、视频、遥测到压缩包（新增遥测目录打包）
            try (ZipOutputStream zos = new ZipOutputStream(new FileOutputStream(zipFile))) {
                addFileToZip(zos, excelFile, ""); // 原有：添加Excel
                addDirectoryToZip(zos, videoDir, "videos"); // 原有：添加视频目录
                addDirectoryToZip(zos, telemetryDir, "telemetries"); // 新增：添加遥测目录
            }

            // 10. 返回压缩包访问路径（确保/files/**映射到localVideoDir）
            String accessPath = "/file/temporaryDirectory/" + zipFileName;
            return AjaxResult.success("多任务导出成功", accessPath);

        } catch (Exception e) {
            System.err.println("多任务导出视频数据失败:" + e);
            e.printStackTrace();
            // 异常时删除不完整压缩包
            if (zipFile.exists() && !zipFile.delete()) {
                System.err.println("无法删除不完整的压缩包: " + zipFile.getAbsolutePath());
            }
            return AjaxResult.error("导出失败: " + e.getMessage());
        } finally {
            // 清理临时工作目录（删除Excel、视频、遥测的临时文件）
            if (tempWorkDir != null) {
                deleteDirectory(tempWorkDir);
            }
        }
    }


    /**
     * 改造：新增telemetryDataList入参，生成遥测数据表
     */
    private void generateExcel(List<Map<String, Object>> taskMapList,
                               List<Map<String, Object>> joinList,
                               List<Map<String, Object>> dataList,
                               List<Map<String, Object>> telemetryDataList, // 新增：遥测数据入参
                               File excelFile) throws IOException {
        try (Workbook workbook = new XSSFWorkbook();
             FileOutputStream fos = new FileOutputStream(excelFile)) {

            // 原有逻辑：生成任务信息、关联信息、视频数据表
            createTaskInfoSheet(workbook, taskMapList);
            createJoinListSheet(workbook, joinList);
            createDataListSheet(workbook, dataList);

            // 新增：生成遥测数据表（会自动判断数据是否为空，空则不创建sheet）
            createTelemetryDataSheet(workbook, telemetryDataList);

            // 将所有工作表写入Excel文件
            workbook.write(fos);
        }
    }


    /**
     * 核心改造：创建多任务信息工作表（从“单任务键值对”改为“多任务表格”）
     */
    private void createTaskInfoSheet(Workbook workbook, List<Map<String, Object>> taskMapList) {
        Sheet sheet = workbook.createSheet("任务信息");
        int rowIndex = 0;

        // 2.1 创建表头（从第一个任务的Map中获取字段名，确保字段一致）
        if (taskMapList.isEmpty()) {
            return;
        }
        Map<String, Object> firstTask = taskMapList.get(0);
        Row headerRow = sheet.createRow(rowIndex++);
        int cellIndex = 0;
        // 表头：字段名（如task_id、task_name、create_time等）
        for (String fieldName : firstTask.keySet()) {
            headerRow.createCell(cellIndex++).setCellValue(fieldName);
        }

        // 2.2 填充多任务数据（每行对应一个任务）
        for (Map<String, Object> taskMap : taskMapList) {
            Row dataRow = sheet.createRow(rowIndex++);
            cellIndex = 0;
            // 按表头顺序填充每个字段的值
            for (String fieldName : firstTask.keySet()) {
                Object value = taskMap.get(fieldName);
                dataRow.createCell(cellIndex++).setCellValue(value != null ? value.toString() : "");
            }
        }

        // 2.3 自动调整列宽（适配所有字段）
        for (int i = 0; i < cellIndex; i++) {
            sheet.autoSizeColumn(i);
        }
    }


    /**
     * 无需改造：多任务关联信息表（原逻辑已支持List<Map>，自动适配多任务）
     */
    private void createJoinListSheet(Workbook workbook, List<Map<String, Object>> joinList) {
        if (joinList == null || joinList.isEmpty()) {
            return;
        }

        Sheet sheet = workbook.createSheet("关联信息");
        int rowIndex = 0;

        // 创建表头（从第一条数据获取字段名）
        Row headerRow = sheet.createRow(rowIndex++);
        Map<String, Object> firstRow = joinList.get(0);
        int cellIndex = 0;
        for (String key : firstRow.keySet()) {
            headerRow.createCell(cellIndex++).setCellValue(key);
        }

        // 填充多任务关联数据（每行对应一条关联记录，含task_id区分任务）
        for (Map<String, Object> rowData : joinList) {
            Row row = sheet.createRow(rowIndex++);
            cellIndex = 0;
            for (Object value : rowData.values()) {
                row.createCell(cellIndex++).setCellValue(value != null ? value.toString() : "");
            }
        }

        // 自动调整列宽
        for (int i = 0; i < cellIndex; i++) {
            sheet.autoSizeColumn(i);
        }
    }


    /**
     * 无需改造：多任务视频数据表（原逻辑已支持List<Map>，自动适配多任务）
     */
    private void createDataListSheet(Workbook workbook, List<Map<String, Object>> dataList) {
        if (dataList == null || dataList.isEmpty()) {
            return;
        }

        Sheet sheet = workbook.createSheet("视频数据");
        int rowIndex = 0;

        // 创建表头（从第一条数据获取字段名，含task_id区分任务）
        Row headerRow = sheet.createRow(rowIndex++);
        Map<String, Object> firstRow = dataList.get(0);
        int cellIndex = 0;
        for (String key : firstRow.keySet()) {
            headerRow.createCell(cellIndex++).setCellValue(key);
        }

        // 填充多任务视频数据（每行对应一条视频记录）
        for (Map<String, Object> rowData : dataList) {
            Row row = sheet.createRow(rowIndex++);
            cellIndex = 0;
            for (Object value : rowData.values()) {
                row.createCell(cellIndex++).setCellValue(value != null ? value.toString() : "");
            }
        }

        // 自动调整列宽
        for (int i = 0; i < cellIndex; i++) {
            sheet.autoSizeColumn(i);
        }
    }

    /**
     * 新增：创建多任务遥测数据表（适配telemetryDataList，每行对应一条遥测记录）
     */
    private void createTelemetryDataSheet(Workbook workbook, List<Map<String, Object>> telemetryDataList) {
        // 若遥测数据为空，不创建工作表（避免生成空sheet）
        if (telemetryDataList == null || telemetryDataList.isEmpty()) {
            return;
        }

        // 1. 创建工作表（命名为“遥测数据”，便于区分）
        Sheet sheet = workbook.createSheet("遥测数据");
        int rowIndex = 0;

        // 2. 创建表头（从第一条遥测数据的Map中获取字段名，确保字段顺序一致）
        Map<String, Object> firstTelemetry = telemetryDataList.get(0);
        Row headerRow = sheet.createRow(rowIndex++);
        int cellIndex = 0;
        // 遍历遥测数据的所有字段名，作为表头（如task_id、telemetry_time、temperature等）
        for (String fieldName : firstTelemetry.keySet()) {
            Cell headerCell = headerRow.createCell(cellIndex++);
            headerCell.setCellValue(fieldName); // 表头值为字段名（如“task_id”“遥测时间”等，取决于数据库字段）
        }

        // 3. 填充遥测数据（每行对应一条遥测记录，自动关联多任务数据）
        for (Map<String, Object> telemetryMap : telemetryDataList) {
            Row dataRow = sheet.createRow(rowIndex++);
            cellIndex = 0;
            // 按表头顺序填充每条遥测记录的字段值（避免字段顺序错乱）
            for (String fieldName : firstTelemetry.keySet()) {
                Object value = telemetryMap.get(fieldName);
                Cell dataCell = dataRow.createCell(cellIndex++);
                // 空值处理：若字段值为null，显示空字符串（避免Excel单元格为空）
                dataCell.setCellValue(value != null ? value.toString() : "");
            }
        }

        // 4. 自动调整列宽（适配字段内容长度，避免文字被截断）
        for (int i = 0; i < cellIndex; i++) {
            // autoSizeColumn：自动计算列宽，true表示忽略合并单元格（常规场景推荐）
            sheet.autoSizeColumn(i, true);
            // 可选：手动调整列宽上限（防止某些字段值过长导致列过宽，如15个字符宽度）
            int currentWidth = sheet.getColumnWidth(i);
            if (currentWidth > 20 * 256) { // Excel列宽单位：1个字符≈256单位，此处限制最大20字符
                sheet.setColumnWidth(i, 20 * 256);
            }
        }
    }


// 以下方法无需改造，直接复用原逻辑
    /**
     * 复制多任务视频目录（原逻辑已支持，dataList含所有任务的storage_path）
     */
    private void copyVideoDirectories(List<Map<String, Object>> dataList, File targetRootDir) throws IOException {
        if (dataList == null || dataList.isEmpty()) {
            return;
        }

        for (Map<String, Object> map : dataList) {
            String storagePath = map.get("storage_path") != null ? map.get("storage_path").toString() : "";
            if (storagePath.isEmpty()) {
                System.err.println("storage_path为空，跳过处理");
                continue;
            }

            File sourceDir = new File(localVideoDir, storagePath);
            if (!sourceDir.exists() || !sourceDir.isDirectory()) {
                System.err.println("视频目录不存在或不是目录: " + sourceDir.getAbsolutePath());
                continue;
            }

            File targetDir = new File(targetRootDir, storagePath);
            if (!targetDir.exists() && !targetDir.mkdirs()) {
                System.err.println("无法创建目标目录: " + targetDir.getAbsolutePath());
                continue;
            }

            copyDirectoryContents(sourceDir, targetDir);
        }
    }

    /**
     * 新增：复制多任务遥测目录（从dataList提取telemetry_storage_path，复制到临时工作目录）
     */
    private void copyTelemetryDirectories(List<Map<String, Object>> dataList, File targetRootDir) throws IOException {
        if (dataList == null || dataList.isEmpty()) {
            return;
        }

        // 遍历所有视频数据，提取遥测路径
        for (Map<String, Object> map : dataList) {
            // 1. 提取telemetry_storage_path（可能为空，需判断）
            String telemetryPath = map.get("telemetry_storage_path") != null ? map.get("telemetry_storage_path").toString().trim() : "";
            if (telemetryPath.isEmpty()) {
                System.err.println("任务[" + map.get("task_id") + "]的telemetry_storage_path为空，跳过处理");
                continue;
            }

            // 2. 构建遥测源目录（注意：telemetry_storage_path可能是相对路径，需拼接baseDir）
            File sourceTelemetryDir = new File(localVideoDir, telemetryPath);
            // 校验源目录合法性
            if (!sourceTelemetryDir.exists()) {
                System.err.println("遥测目录不存在: " + sourceTelemetryDir.getAbsolutePath());
                continue;
            }
            if (!sourceTelemetryDir.isDirectory()) {
                System.err.println("遥测路径不是目录: " + sourceTelemetryDir.getAbsolutePath());
                continue;
            }

            // 3. 构建临时目标目录（放在临时工作目录的"telemetries"文件夹下，与视频目录"videos"区分）
            // 用遥测路径的相对结构作为子目录，避免多任务遥测文件冲突
            File targetTelemetryDir = new File(targetRootDir, "telemetries/" + telemetryPath);
            // 递归创建目标目录（父目录不存在时自动创建）
            if (!targetTelemetryDir.exists() && !targetTelemetryDir.mkdirs()) {
                System.err.println("无法创建遥测目标目录: " + targetTelemetryDir.getAbsolutePath());
                continue;
            }

            // 4. 复制遥测目录内容（复用已有的copyDirectoryContents方法，无需重复写逻辑）
            copyDirectoryContents(sourceTelemetryDir, targetTelemetryDir);
            System.out.println("复制遥测目录成功: " + sourceTelemetryDir.getAbsolutePath() + " → " + targetTelemetryDir.getAbsolutePath());
        }
    }

    /**
     * 复制目录内容（无需改造）
     */
    private void copyDirectoryContents(File sourceDir, File targetDir) throws IOException {
        File[] files = sourceDir.listFiles();
        if (files == null) {
            System.err.println("无法列出目录内容: " + sourceDir.getAbsolutePath());
            return;
        }

        for (File file : files) {
            File targetFile = new File(targetDir, file.getName());

            if (file.isDirectory()) {
                if (!targetFile.mkdir()) {
                    System.err.println("无法创建子目录: " + targetFile.getAbsolutePath());
                    continue;
                }
                copyDirectoryContents(file, targetFile);
            } else {
                try (InputStream is = new FileInputStream(file);
                     OutputStream os = new FileOutputStream(targetFile)) {

                    byte[] buffer = new byte[4096];
                    int length;
                    while ((length = is.read(buffer)) > 0) {
                        os.write(buffer, 0, length);
                    }
                }
                System.err.println("复制文件成功: " + file.getName() + " 到 " + targetFile.getAbsolutePath());
            }
        }
    }



    /**
     * 添加文件到压缩包（无需改造）
     */
    private void addFileToZip(ZipOutputStream zos, File file, String baseDir) throws IOException {
        if (file.isHidden()) {
            return;
        }

        String entryName = baseDir + file.getName();
        ZipEntry zipEntry = new ZipEntry(entryName);
        zos.putNextEntry(zipEntry);

        try (FileInputStream fis = new FileInputStream(file)) {
            byte[] buffer = new byte[1024];
            int length;
            while ((length = fis.read(buffer)) > 0) {
                zos.write(buffer, 0, length);
            }
        }
        zos.closeEntry();
    }

    /**
     * 添加目录到压缩包（无需改造）
     */
    private void addDirectoryToZip(ZipOutputStream zos, File dir, String baseDir) throws IOException {
        if (!dir.exists() || !dir.isDirectory()) {
            return;
        }

        File[] files = dir.listFiles();
        if (files == null) {
            return;
        }

        String newBaseDir = baseDir + (baseDir.isEmpty() ? "" : "/") + dir.getName() + "/";

        for (File file : files) {
            if (file.isDirectory()) {
                addDirectoryToZip(zos, file, newBaseDir);
            } else {
                addFileToZip(zos, file, newBaseDir);
            }
        }
    }

    /**
     * 删除目录（无需改造）
     */
    private boolean deleteDirectory(File dir) {
        if (dir == null || !dir.exists()) {
            return true;
        }

        File[] files = dir.listFiles();
        if (files != null) {
            for (File file : files) {
                if (file.isDirectory()) {
                    deleteDirectory(file);
                } else {
                    if (!file.delete()) {
                        System.err.println("无法删除临时文件: " + file.getAbsolutePath());
                    }
                }
            }
        }

        return dir.delete();
    }


    //************************************* 导入方法 ****************************************************


    /**
     * 导入视频任务数据（适配多任务：支持解析导出的多任务Zip包）
     */
    @PostMapping("/importVideoDataTask")
    @ResponseBody
    public AjaxResult importVideoDataTask(@RequestParam("file") MultipartFile file) {
        if (file.isEmpty()) {
            return AjaxResult.error("导入文件不能为空");
        }

        // 临时目录用于解压文件
        File tempDir = null;
        try {
            // 1. 创建临时目录
            tempDir = File.createTempFile("video_import_", "_temp");
            if (!tempDir.delete()) {
                throw new IOException("无法删除临时文件以创建目录");
            }
            if (!tempDir.mkdir()) {
                throw new IOException("无法创建临时目录");
            }

            // 2. 解压Zip文件（原逻辑不变，支持多任务的Excel和视频目录）
            try (ZipInputStream zis = new ZipInputStream(file.getInputStream())) {
                ZipEntry entry;
                while ((entry = zis.getNextEntry()) != null) {
                    String entryName = entry.getName();
                    File entryFile = new File(tempDir, entryName);

                    if (entry.isDirectory()) {
                        if (!entryFile.mkdirs()) {
                            System.err.println("无法创建目录: " + entryFile.getAbsolutePath());
                        }
                    } else {
                        // 创建父目录（避免多级目录缺失）
                        File parentDir = entryFile.getParentFile();
                        if (!parentDir.exists() && !parentDir.mkdirs()) {
                            System.err.println("无法创建父目录: " + parentDir.getAbsolutePath());
                        }
                        // 写入文件
                        try (FileOutputStream fos = new FileOutputStream(entryFile)) {
                            byte[] buffer = new byte[4096];
                            int length;
                            while ((length = zis.read(buffer)) > 0) {
                                fos.write(buffer, 0, length);
                            }
                        }
                    }
                    zis.closeEntry();
                }
            }

            // 3. 查找Excel文件（原逻辑不变，支持多任务Excel）
            File excelFile = findExcelFile(tempDir);
            if (excelFile == null) {
                return AjaxResult.error("未找到导入的Excel文件（格式：xlsx）");
            }

            // 4. 解析Excel数据（核心改造：任务信息从Map改为List<Map>）
            List<Map<String, Object>> taskMapList; // 多任务列表
            List<Map<String, Object>> joinList;    // 多任务关联数据
            List<Map<String, Object>> dataList;    // 多任务视频数据
            List<Map<String, Object>> telemetryList; // 新增：遥测数据列表
            try (FileInputStream fis = new FileInputStream(excelFile);
                 Workbook workbook = new XSSFWorkbook(fis)) {

                // 4.1 解析多任务信息表（改造后方法）
                taskMapList = parseTaskInfoSheet(workbook.getSheet("任务信息"));
                if (taskMapList == null || taskMapList.isEmpty()) {
                    return AjaxResult.error("任务信息表数据为空，无法导入");
                }

                // 4.2 解析关联信息表（原逻辑不变，天然支持多任务）
                joinList = parseJoinListSheet(workbook.getSheet("关联信息"));

                // 4.3 解析视频数据表（原逻辑不变，天然支持多任务）
                dataList = parseDataListSheet(workbook.getSheet("视频数据"));
                if (dataList == null || dataList.isEmpty()) {
                    return AjaxResult.error("视频数据表数据为空，无法导入");
                }
                // 4.4 新增：解析遥测数据（适配导出的“遥测数据”sheet）
                telemetryList = parseTelemetryDataSheet(workbook.getSheet("遥测数据"));
                System.err.println("解析到遥测数据条数：" + telemetryList.size());

                // 4.4 关键：给视频数据关联任务ID（确保视频归属正确的任务）
                dataList = bindTaskIdToVideoData(taskMapList, dataList);
            }

            // 5. 处理视频文件（复制到存储目录，原逻辑优化：适配多任务目录结构）
            File videoSourceDir = new File(tempDir, "videos");
            if (videoSourceDir.exists() && videoSourceDir.isDirectory()) {
                copyImportedVideos(videoSourceDir, localVideoDir);
            } else {
                System.err.println("未找到视频目录，跳过视频复制");
            }

            // 新增：5.1 处理遥测目录（对应导出的telemetries目录）
            File telemetrySourceDir = new File(tempDir, "telemetries");
            if (telemetrySourceDir.exists() && telemetrySourceDir.isDirectory()) {
                copyImportedTelemetries(telemetrySourceDir, localVideoDir); // 调用新增的遥测复制方法
            } else {
                System.err.println("未找到遥测目录，跳过遥测复制");
            }

            // 6. 批量保存多任务数据到数据库（改造：调用批量保存方法）
            int totalResult = videoTaskService.batchImportVideoTaskData(taskMapList, joinList, dataList,telemetryList);
            return AjaxResult.success("多任务导入成功，共处理 " + totalResult + " 条数据（含任务、关联、视频数据）");

        } catch (Exception e) {
            System.err.println("多任务导入视频数据失败:" + e);
            e.printStackTrace();
            return AjaxResult.error("导入失败：" + e.getMessage());
        } finally {
            // 清理临时文件（原逻辑不变）
            if (tempDir != null) {
                deleteDirectory(tempDir);
            }
        }
    }


    /**
     * 新增：解析多任务遥测数据表（适配导出的“遥测数据”sheet）
     */
    private List<Map<String, Object>> parseTelemetryDataSheet(Sheet sheet) {
        // 遥测表可能为空（部分任务无遥测数据），返回空列表而非null
        if (sheet == null) {
            return new ArrayList<>();
        }

        List<Map<String, Object>> telemetryList = new ArrayList<>();
        int rowCount = sheet.getLastRowNum();
        if (rowCount < 1) { // 至少需要“表头行 + 1行数据”，无数据则返回空列表
            return telemetryList;
        }

        // 1. 读取遥测表表头（如task_id、telemetry_time、temperature等）
        Row headerRow = sheet.getRow(0);
        List<String> headers = new ArrayList<>();
        for (int colIdx = 0; colIdx < headerRow.getLastCellNum(); colIdx++) {
            Cell cell = headerRow.getCell(colIdx);
            String header = cell != null ? getCellValue(cell).trim() : "";
            if (!header.isEmpty()) { // 过滤空表头
                headers.add(header);
            }
        }

        // 2. 读取每行遥测数据（每行对应一条遥测记录，关联task_id）
        for (int rowIdx = 1; rowIdx <= rowCount; rowIdx++) {
            Row dataRow = sheet.getRow(rowIdx);
            if (dataRow == null) {
                continue; // 跳过空行
            }

            Map<String, Object> telemetryMap = new HashMap<>();
            // 按表头顺序填充字段值（确保与导出时的字段顺序一致）
            for (int colIdx = 0; colIdx < headers.size(); colIdx++) {
                String header = headers.get(colIdx);
                Cell cell = dataRow.getCell(colIdx); // 可能存在单元格为空的情况（允许部分字段空值）
                telemetryMap.put(header, cell != null ? getCellValue(cell) : "");
            }

            // 过滤无效遥测记录（必须包含task_id，否则无法关联任务）
            if (telemetryMap.containsKey("task_id") && !telemetryMap.get("task_id").toString().isEmpty()) {
                telemetryList.add(telemetryMap);
            } else {
                System.err.println("跳过无效遥测记录（缺少task_id）：" + telemetryMap);
            }
        }

        return telemetryList;
    }


    /**
     * 核心改造：解析多任务信息表（从单任务Map改为多任务List<Map>）
     */
    private List<Map<String, Object>> parseTaskInfoSheet(Sheet sheet) {
        if (sheet == null) {
            return null;
        }

        List<Map<String, Object>> taskMapList = new ArrayList<>();
        int rowCount = sheet.getLastRowNum();
        if (rowCount < 1) { // 至少需要“表头行 + 1行数据”
            return taskMapList;
        }

        // 1. 读取表头（第一行：字段名，如task_id、task_name、create_time等）
        Row headerRow = sheet.getRow(0);
        List<String> headers = new ArrayList<>();
        for (int i = 0; i < headerRow.getLastCellNum(); i++) {
            Cell cell = headerRow.getCell(i);
            headers.add(cell != null ? getCellValue(cell).trim() : "");
        }

        // 2. 读取每行数据（每行对应一个任务）
        for (int rowIdx = 1; rowIdx <= rowCount; rowIdx++) {
            Row dataRow = sheet.getRow(rowIdx);
            if (dataRow == null) {
                continue;
            }

            Map<String, Object> taskMap = new HashMap<>();
            // 按表头顺序填充字段值
            for (int colIdx = 0; colIdx < headers.size(); colIdx++) {
                String header = headers.get(colIdx);
                if (header.isEmpty()) {
                    continue; // 跳过空表头
                }
                Cell cell = dataRow.getCell(colIdx);
                String cellValue = cell != null ? getCellValue(cell) : "";
                taskMap.put(header, cellValue);
            }

            // 过滤空任务（至少包含task_id等关键字段）
            if (taskMap.containsKey("task_id") && !taskMap.get("task_id").toString().isEmpty()) {
                taskMapList.add(taskMap);
            }
        }

        return taskMapList;
    }


    /**
     * 新增：给视频数据绑定任务ID（确保多任务场景下视频归属正确）
     * 逻辑：通过视频数据中的“task_id”字段，关联到任务列表中的任务
     */
    private List<Map<String, Object>> bindTaskIdToVideoData(List<Map<String, Object>> taskMapList,
                                                            List<Map<String, Object>> dataList) {
//        System.err.println("dataList-----------------"+dataList);
//        if (taskMapList.isEmpty() || dataList.isEmpty()) {
//            return dataList;
//        }
//
//        // 1. 构建任务ID到任务信息的映射（便于快速匹配）
//        Map<String, Map<String, Object>> taskIdMap = new HashMap<>();
//        for (Map<String, Object> taskMap : taskMapList) {
//            String taskId = taskMap.get("task_id") != null ? taskMap.get("task_id").toString() : "";
//            if (!taskId.isEmpty()) {
//                taskIdMap.put(taskId, taskMap);
//            }
//        }
//
//        // 2. 给每个视频数据绑定完整任务信息（或校验task_id有效性）
//        for (Map<String, Object> videoData : dataList) {
//            String videoTaskId = videoData.get("task_id") != null ? videoData.get("task_id").toString() : "";
//            if (taskIdMap.containsKey(videoTaskId)) {
//                // 可选：将任务的完整信息（如task_name）关联到视频数据，便于后续处理
//                videoData.put("task_info", taskIdMap.get(videoTaskId));
//            } else {
//                throw new IllegalArgumentException("视频数据中的task_id=" + videoTaskId + " 未在任务列表中找到，导入失败");
//            }
//        }

        return dataList;
    }


    /**
     * 优化：复制导入的视频文件（适配多任务目录结构，修复原路径计算逻辑）
     * 原逻辑“往上走两级目录”可能导致路径错误，改为“保留导出时的storage_path结构”
     */
    private void copyImportedVideos(File sourceDir, String targetRootDir) throws IOException {
        File[] files = sourceDir.listFiles();
        if (files == null) {
            return;
        }

        // 遍历视频目录下的所有子目录（对应多任务的storage_path结构）
        for (File file : files) {
            // 关键：计算视频文件相对于“videos”目录的相对路径（即导出时的storage_path）
            Path sourcePath = file.toPath();
            Path videosDirPath = sourceDir.toPath();
            Path relativePath = videosDirPath.relativize(sourcePath); // 如“task1/video1.mp4”或“202409/task2/video2.mp4”

            // 构建目标路径：存储根目录 + storage_path（与导出时的路径一致）
            File targetFile = new File(targetRootDir, relativePath.toString());

            if (file.isDirectory()) {
                // 递归创建子目录（保留多任务的目录结构）
                if (!targetFile.exists() && !targetFile.mkdirs()) {
                    System.err.println("无法创建目标视频目录: " + targetFile.getAbsolutePath());
                }
                copyImportedVideos(file, targetRootDir);
            } else {
                // 确保父目录存在（避免多级目录缺失）
                File parentDir = targetFile.getParentFile();
                if (!parentDir.exists() && !parentDir.mkdirs()) {
                    System.err.println("无法创建视频父目录: " + parentDir.getAbsolutePath());
                    continue;
                }

                // 复制视频文件（原逻辑不变，提升效率）
                try (InputStream is = new FileInputStream(file);
                     OutputStream os = new FileOutputStream(targetFile)) {

                    byte[] buffer = new byte[8192]; // 增大缓冲区，提升大视频复制速度
                    int length;
                    while ((length = is.read(buffer)) > 0) {
                        os.write(buffer, 0, length);
                    }
                }
                System.err.println("复制导入视频成功: 源路径=" + file.getAbsolutePath() + " → 目标路径=" + targetFile.getAbsolutePath());
            }
        }
    }


    /**
     * 新增：复制导入的遥测目录（对应导出时的telemetries目录，还原遥测文件）
     */
    private void copyImportedTelemetries(File sourceDir, String targetRootDir) throws IOException {
        // 校验遥测源目录是否存在
        if (sourceDir == null || !sourceDir.exists() || !sourceDir.isDirectory()) {
            System.err.println("遥测源目录不存在或非法：" + (sourceDir != null ? sourceDir.getAbsolutePath() : "null"));
            return;
        }

        File[] files = sourceDir.listFiles();
        if (files == null) {
            return;
        }

        // 遍历遥测目录下的所有文件/子目录（保留导出时的相对路径结构）
        for (File file : files) {
            // 计算遥测文件相对于“telemetries”目录的相对路径（即导出时的telemetry_storage_path）
            Path sourcePath = file.toPath();
            Path telemetryDirPath = sourceDir.toPath();
            Path relativePath = telemetryDirPath.relativize(sourcePath);

            // 构建目标路径：存储根目录 + 遥测相对路径（与导出时的telemetry_storage_path完全一致）
            File targetFile = new File(targetRootDir, relativePath.toString());

            if (file.isDirectory()) {
                // 递归创建子目录（保留多级目录结构）
                if (!targetFile.exists() && !targetFile.mkdirs()) {
                    System.err.println("无法创建目标遥测目录：" + targetFile.getAbsolutePath());
                }
                copyImportedTelemetries(file, targetRootDir);
            } else {
                // 确保父目录存在（避免多级目录缺失导致文件写入失败）
                File parentDir = targetFile.getParentFile();
                if (!parentDir.exists() && !parentDir.mkdirs()) {
                    System.err.println("无法创建遥测父目录：" + parentDir.getAbsolutePath());
                    continue;
                }

                // 复制遥测文件（复用视频复制的IO逻辑，确保文件完整性）
                try (InputStream is = new FileInputStream(file);
                     OutputStream os = new FileOutputStream(targetFile)) {

                    byte[] buffer = new byte[4096];
                    int length;
                    while ((length = is.read(buffer)) > 0) {
                        os.write(buffer, 0, length);
                    }
                }
                System.out.println("复制导入遥测文件成功：源路径=" + file.getAbsolutePath() + " → 目标路径=" + targetFile.getAbsolutePath());
            }
        }
    }


    /**
     * 以下方法无需改造，直接复用（已天然支持多任务）
     */

// 查找Excel文件（原逻辑不变）
    private File findExcelFile(File dir) {
        File[] files = dir.listFiles();
        if (files == null) {
            return null;
        }

        for (File file : files) {
            if (file.isFile() && file.getName().endsWith(".xlsx")) {
                return file;
            }
            File found = findExcelFile(file);
            if (found != null) {
                return found;
            }
        }
        return null;
    }

    // 解析关联信息表（原逻辑不变，支持多任务）
    private List<Map<String, Object>> parseJoinListSheet(Sheet sheet) {
        if (sheet == null) {
            return new ArrayList<>();
        }

        List<Map<String, Object>> joinList = new ArrayList<>();
        int rowCount = sheet.getLastRowNum();
        if (rowCount < 1) {
            return joinList;
        }

        // 读取表头
        Row headerRow = sheet.getRow(0);
        List<String> headers = new ArrayList<>();
        for (int i = 0; i < headerRow.getLastCellNum(); i++) {
            Cell cell = headerRow.getCell(i);
            headers.add(cell != null ? getCellValue(cell).trim() : "");
        }

        // 读取每行关联数据（含task_id，区分多任务）
        for (int i = 1; i <= rowCount; i++) {
            Row row = sheet.getRow(i);
            if (row == null) {
                continue;
            }

            Map<String, Object> rowData = new HashMap<>();
            for (int j = 0; j < headers.size(); j++) {
                String header = headers.get(j);
                if (header.isEmpty()) {
                    continue;
                }
                Cell cell = row.getCell(j);
                rowData.put(header, cell != null ? getCellValue(cell) : "");
            }
            joinList.add(rowData);
        }

        return joinList;
    }

    // 解析视频数据表（原逻辑不变，支持多任务）
    private List<Map<String, Object>> parseDataListSheet(Sheet sheet) {
        if (sheet == null) {
            return new ArrayList<>();
        }

        List<Map<String, Object>> dataList = new ArrayList<>();
        int rowCount = sheet.getLastRowNum();
        if (rowCount < 1) {
            return dataList;
        }

        // 读取表头
        Row headerRow = sheet.getRow(0);
        List<String> headers = new ArrayList<>();
        for (int i = 0; i < headerRow.getLastCellNum(); i++) {
            Cell cell = headerRow.getCell(i);
            headers.add(cell != null ? getCellValue(cell).trim() : "");
        }

        // 读取每行视频数据（含task_id，区分多任务）
        for (int i = 1; i <= rowCount; i++) {
            Row row = sheet.getRow(i);
            if (row == null) {
                continue;
            }

            Map<String, Object> rowData = new HashMap<>();
            for (int j = 0; j < headers.size(); j++) {
                String header = headers.get(j);
                if (header.isEmpty()) {
                    continue;
                }
                Cell cell = row.getCell(j);
                rowData.put(header, cell != null ? getCellValue(cell) : "");
            }
            dataList.add(rowData);
        }

        return dataList;
    }

    // 获取单元格值（原逻辑不变，适配各种数据类型）
    private String getCellValue(Cell cell) {
        if (cell == null) {
            return "";
        }

        switch (cell.getCellType()) {
            case STRING:
                return cell.getStringCellValue().trim(); // 去空格，避免无效字符
            case NUMERIC:
                if (DateUtil.isCellDateFormatted(cell)) {
                    // 日期格式统一为yyyy-MM-dd HH:mm:ss
                    return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(cell.getDateCellValue());
                } else {
                    // 处理数字（如task_id），避免科学计数法（如123456789 → 1.23456789E8）
                    return new BigDecimal(cell.getNumericCellValue()).toPlainString();
                }
            case BOOLEAN:
                return String.valueOf(cell.getBooleanCellValue());
            case FORMULA:
                try {
                    // 公式单元格取计算结果
                    return getCellValue(cell);
                } catch (Exception e) {
                    return cell.getCellFormula(); // 公式计算失败时返回公式本身
                }
            default:
                return "";
        }
    }



    @PostMapping("pathPackagingCompressionDownload")
    @ResponseBody
    public AjaxResult pathPackagingCompressionDownload(@RequestBody JSONObject object) {

        // 获取filePaths数组
        JSONArray filePathArray = object.getJSONArray("filePaths");
        if (filePathArray == null || filePathArray.isEmpty()) {
            return AjaxResult.error("请选择要下载的文件");
        }

        // 转换为String数组
        String[] filePaths = new String[filePathArray.size()];
        for (int i = 0; i < filePathArray.size(); i++) {
            filePaths[i] = filePathArray.getString(i);
        }

        // 验证输入参数
        if (filePaths == null || filePaths.length == 0) {
            return AjaxResult.error("请选择要下载的文件");
        }

        // 创建downloadZip目录
        String downloadZipDir = localVideoDir + File.separator + "downloadZip";
        File zipDir = new File(downloadZipDir);
        if (!zipDir.exists()) {
            zipDir.mkdirs();
        }

        // 生成时间戳文件名
        String timestamp = new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
        String zipFileName = timestamp + ".zip";
        String zipFilePath = downloadZipDir + File.separator + zipFileName;

        // 创建ZIP文件（核心调整：只保留文件名，不包含目录）
        try (ZipOutputStream zipOut = new ZipOutputStream(new FileOutputStream(zipFilePath))) {
            for (String fullPath : filePaths) {
                if (StrUtil.isBlank(fullPath)) {
                    continue;
                }

                File file = new File(fullPath);
                // 验证文件是否存在
                if (!file.exists() || !file.isFile()) {
                    throw new RuntimeException("文件不存在或不是有效文件: " + fullPath);
                }

                // 获取纯文件名（不含任何目录）
                String fileName = file.getName();

                // 添加文件到ZIP，使用纯文件名作为entry名（关键：去掉目录结构）
                ZipEntry zipEntry = new ZipEntry(fileName);
                zipOut.putNextEntry(zipEntry);

                // 写入文件内容
                try (FileInputStream fis = new FileInputStream(file)) {
                    byte[] buffer = new byte[1024 * 8];
                    int len;
                    while ((len = fis.read(buffer)) != -1) {
                        zipOut.write(buffer, 0, len);
                    }
                }
                zipOut.closeEntry(); // 关闭当前entry
            }

            // 返回下载路径
            String downloadUrl = "/file/downloadZip/" + zipFileName;
            return AjaxResult.success(downloadUrl);

        } catch (IOException e) {
            FileUtil.del(zipFilePath);
            return AjaxResult.error("打包失败: " + e.getMessage());
        } catch (RuntimeException e) {
            FileUtil.del(zipFilePath);
            return AjaxResult.error(e.getMessage());
        }
    }

    /**
     * 添加文件到ZIP压缩包
     */
    private void addFileToZipNew(ZipOutputStream zipOut, File file, String entryName) throws IOException {
        try (InputStream in = new FileInputStream(file)) {
            ZipEntry zipEntry = new ZipEntry(entryName);
            zipOut.putNextEntry(zipEntry);

            byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲区
            int bytesRead;
            while ((bytesRead = in.read(buffer)) != -1) {
                zipOut.write(buffer, 0, bytesRead);
            }

            zipOut.closeEntry();
        }
    }




}
